feat: add DeepSeek + GLM Coding Plan providers, fix DeepSeek reasonin…#604
feat: add DeepSeek + GLM Coding Plan providers, fix DeepSeek reasonin…#604bao-byterover merged 10 commits intomainfrom
Conversation
…g detection Adds two LLM providers to bring byterover-cli to opencode parity for the four user-requested integrations (DeepSeek, MiniMax, Moonshot/Kimi, Z.AI): - DeepSeek (deepseek-chat, deepseek-reasoner) via @ai-sdk/openai-compatible against https://api.deepseek.com/v1, env DEEPSEEK_API_KEY. - GLM Coding Plan as a separate provider ID (glm-coding-plan) pointing at the Z.AI Coding Plan subscription endpoint (api.z.ai/api/coding/paas/v4), reusing ZHIPU_API_KEY. Coexists with the existing 'glm' provider so standard pay-per-token users aren't silently switched onto a subscription. Bug fix: deepseek-r1/deepseek-reasoner were mapped to reasoningFormat 'think-tags' in model-capabilities.ts, but the DeepSeek OpenAI-compatible API streams reasoning through the native 'reasoning_content' field — the old path never matched, so reasoning was effectively broken. Now correctly mapped to native-field with reasoningField: 'reasoning_content'. MiniMax, Moonshot/Kimi, and standard GLM were already wired and work unchanged. The Kimi Coding Plan subscription tier uses the same Moonshot endpoint, so no new provider entry is needed for it (matches opencode's behavior — opencode also has no kimi-coding-plan provider). Plan: plan/llm-providers/{DESIGN,IMPLEMENTATION}.md TDD-authored; 21 new test assertions across registry, capability, and provider-module shape. Full suite: 7316 passing, 16 pending. 0 lint errors. Icon asset for DeepSeek sourced from Wikimedia Commons (MIT-licensed), whale-only path with cropped viewBox. GLM Coding Plan reuses the existing zai icon. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
Claude finished @ngduyanhece's task in 3m 16s —— View job PR Review: feat: DeepSeek + GLM Coding Plan providers, fix DeepSeek reasoning
SummaryOverall score: 4 / 5 This is a well-executed, well-scoped PR. The TDD sequence is evidenced clearly, the bug fix rationale is sound, and the new providers follow the established module pattern precisely. No security issues found. The issues below are all minor maintainability and documentation concerns. What's Good
Issues (inline comments posted)
None of these block merging — the most actionable before merge are the |
…-sync-main Chore/llm providers expansion sync main
|
Claude finished @cuongdo-byterover's task in 2m 1s —— View job Updating DeepSeek icon to black-and-white
The change is committed locally but the push was denied by GitHub. The diff is: -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 41.3595">
- <path fill="#4d6bfe" d="..."/>
+<svg width="20" height="20" viewBox="0 0 56 41.3595" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path fill="#D4D4D4" d="..."/>To apply this, please apply the same edit to
|
Was pointing at the docs landing page (docs.z.ai/devpack/overview) which buries the actual API-key UI several clicks deep. Direct link to the key-management page makes onboarding one click. Reported in ENG-2603 QA round.
…alidating Validation was sending one chat-completion request keyed on knownModels[0]. For GLM Coding Plan that means glm-4.7 — but the coding-plan tier on Z.AI doesn't yet serve glm-4.7, only glm-4.5/4.5-flash etc. The endpoint returns a model-not-found error that the previous handler couldn't disambiguate from a real auth failure, so a valid key was reported as invalid. Now validateApiKey iterates through every entry in knownModels: - 200/202 : key valid, return immediately - 401 : key invalid, return immediately (do not try further models) - 403 : key has no permission, return immediately - 400 / 404 : likely "model not on this tier" — try the next model - 429 / 5xx : key was accepted (server-side issue), return valid - network : try next model; if all fail, return invalid + last error If knownModels is empty we fall back to a single 'default' model probe. Adds 9 new test cases covering the first-success path, model-skip path, auth-error short-circuit, rate-limit handling, server-error handling, all- models-fail path, and the empty knownModels fallback. Reported in ENG-2603 QA round.
DeepSeek-R1 (and any provider that emits reasoning_content during a turn)
rejects the next turn's request with "The reasoning_content in the thinking
mode must be passed back to the API" if the assistant message in the
conversation history doesn't carry that prior reasoning. The curate flow
hit this on the second iteration of every multi-step query: turn 1 ran
fine, turn 2 errored before any tool could run.
Three gaps wired:
1. AiSdkContentGenerator.generateContent now copies result.reasoningText
(Vercel AI SDK already extracts it from reasoning_content) into
GenerateContentResponse.reasoning. Streaming variant already emitted
per-delta reasoning chunks; only non-streaming was lossy.
2. AgentLLMService now persists reasoning into the assistant InternalMessage
in both code paths — non-streaming via response.reasoning, streaming via
accumulating chunk.reasoning across THINKING chunks.
3. convertAssistantMessage now emits a {type: 'reasoning', text} part on
the AI-SDK ModelMessage when msg.reasoning is present. The reasoning
part is placed FIRST in the parts array so providers that key on
ordering see it as the start of the assistant turn.
Adds 4 regression tests covering: reasoning + text, reasoning + tool-call,
text-only no-regression path, and the empty-message guard.
Reported in ENG-2603 QA round.
The TUI/WebUI provider-setup screen reads apiKeyUrl from src/server/core/domain/entities/provider-registry.ts, not from the agent-side provider module. Earlier patch fixed only the agent-side copy, so the user-visible 'Get your API key at' link still pointed at docs.z.ai/devpack/overview. This patches the server-side registry to match.
Six items from the inline review: 1. SVG: remove duplicate <title> on the inner <g> in deepseek-provider.svg so screen readers announce the icon name once, not twice. 2. ai-sdk-message-converter: drop the redundant `hasReasoning && msg.reasoning` double-guard. The Boolean() check produced no narrowing benefit; a single `if (msg.reasoning)` is sufficient. 3. provider-model-fetchers: expand the comment on the validateApiKey fall-through branch to call out that ECONNREFUSED / no-response axios errors are also treated as "key accepted" via the same path. Documented as an optimistic preference for false-positive valid over false-negative invalid. 4. ai-sdk-message-converter test: hoist the toModelMessages describe out of the toAiSdkTools describe so reporters attribute the new tests to the correct parent suite. Added a focused eslint-disable for mocha/max-top-level-suites since this file genuinely tests two unrelated exports. 5. provider-model-fetchers test: fix indentation of the afterEach() body and the closing brace inside the ChatBasedModelFetcher.validateApiKey describe. 6. ai-sdk-message-converter test: add a regression test pinning the reasoning-only path (msg.reasoning set, but content/toolCalls absent) that returns a parts array with a single reasoning entry. All 6347 unit tests pass; lint clean; typecheck clean.
Fix/eng 2603
|
Claude finished @cuongdo-byterover's task in 1m 25s —— View job Updating DeepSeek icon to black-and-white
The change is committed locally but the push was denied by GitHub. The diff is: -<svg viewBox="18 21 28 23" xmlns="http://www.w3.org/2000/svg">
+<svg width="20" height="20" viewBox="18 21 28 23" fill="none" xmlns="http://www.w3.org/2000/svg">The fill color ( To apply this, please edit |

Summary
think-tagsparser that never matched the API's actualreasoning_contentfield — reasoning output was silently dropped.deepseekprovider via@ai-sdk/openai-compatibleagainsthttps://api.deepseek.com/v1(envDEEPSEEK_API_KEY).glm-coding-planprovider as a separate ID alongside the existingglm, pointing athttps://api.z.ai/api/coding/paas/v4, reusingZHIPU_API_KEY.deepseek-r1/deepseek-reasonernow correctly map toreasoningFormat: 'native-field'withreasoningField: 'reasoning_content'.zaiicon.glmwere already wired and untouched. Nokimi-coding-planprovider, and Moonshot's coding-plan tier uses the sameapi.moonshot.ai/v1endpoint as the standard provider.zhipuai-coding-plan(mainland-CN),minimax-coding-plan(Anthropic-compatible), and full models.dev catalog adoption are deferred perplan/llm-providers/DESIGN.mdD3/D4.Type of change
Scope (select all touched areas)
Linked issues
Root cause (bug fixes only)
getModelCapabilities()insrc/agent/infra/llm/model-capabilities.tsmappeddeepseek-r1/deepseek-reasonertoreasoningFormat: 'think-tags'. DeepSeek's OpenAI-compatible API does not emit<think>...</think>markers — it streams reasoning in a nativereasoning_contentfield. The think-tag scanner never matched, so reasoning content was effectively dropped on the floor.brv providers connectand never exercised end-to-end. The capability rule sat in a code path that no integration test could trigger. The fix is forced by adding the provider; this PR closes the loop in one go.Test plan
test/unit/agent/llm/model-capabilities.test.ts(new) — 3 assertions for the capability fixtest/unit/agent/llm/providers/deepseek.test.ts(new) — 6 assertions for provider-module shapetest/unit/agent/llm/providers/glm-coding-plan.test.ts(new) — 7 assertions for provider-module shapetest/unit/core/domain/entities/provider-registry.test.ts(extended) — 13 new assertions (6 DeepSeek + 7 GLM Coding Plan)deepseek-reasoneranddeepseek-r1resolve tonative-field/reasoning_content;deepseek-chatreports no reasoningglm-coding-plancoexists withglm(regression check thatgetProviderById('glm')still resolves and the two have distinct base URLs)providerRequiresApiKey('deepseek')andproviderRequiresApiKey('glm-coding-plan')both returntrueUser-visible changes
brv providers connectpicker: DeepSeek and GLM Coding Plan (Z.AI).deepseek-reasoner,deepseek-r1) now stream reasoning into the dedicated thinking block instead of silently dropping it.Evidence
TDD sequence (full output in commit history):
Targeted run after Phase 2 (DeepSeek + GLM Coding Plan):
Checklist
npm test— 7316 passing)npm run lint— 0 errors)npm run typecheck— root + webui clean)npm run build)feat:prefix)mainRisks and mitigations
glm-coding-planincludesglm-5-turbo.provider-model-fetcher-registry.ts. Smoke test with a subscription holder is documented as a follow-up inplan/llm-providers/IMPLEMENTATION.md§2.3.deepseek-reasoneraccessible via OpenRouter (or any other route that flows throughgetModelCapabilities).think-tagsmapping never matched a real DeepSeek response, so users were not seeing reasoning anyway — this is a fix, not a regression. OpenRouter has its own capability detection path that is unaffected.glmusers on the Z.AI Coding Plan subscription might not realize they need to switch to the newglm-coding-planprovider to consume their subscription quota.GLM (Z.AI)vsGLM Coding Plan (Z.AI)) and the description string call out the subscription. README table makes both explicit.